using System;
using System.Diagnostics;
using System.Text;

using u8 = System.Byte;
using u32 = System.UInt32;

namespace Community.CsharpSqlite
{
  public partial class Sqlite3
  {
    /*
    ** 2001 September 15
    **
    ** The author disclaims copyright to this source code.  In place of
    ** a legal notice, here is a blessing:
    **
    **    May you do good and not evil.
    **    May you find forgiveness for yourself and forgive others.
    **    May you share freely, never taking more than you give.
    **
    *************************************************************************
    ** This file contains C code routines that are called by the parser
    ** in order to generate code for DELETE FROM statements.
    *************************************************************************
    **  Included in SQLite3 port to C#-SQLite;  2008 Noah B Hart
    **  C#-SQLite is an independent reimplementation of the SQLite software library
    **
    **  SQLITE_SOURCE_ID: 2011-06-23 19:49:22 4374b7e83ea0a3fbc3691f9c0c936272862f32f2
    **
    *************************************************************************
    */
    //#include "sqliteInt.h"

    /*
    ** While a SrcList can in general represent multiple tables and subqueries
    ** (as in the FROM clause of a SELECT statement) in this case it contains
    ** the name of a single table, as one might find in an INSERT, DELETE,
    ** or UPDATE statement.  Look up that table in the symbol table and
    ** return a pointer.  Set an error message and return NULL if the table 
    ** name is not found or if any other error occurs.
    **
    ** The following fields are initialized appropriate in pSrc:
    **
    **    pSrc->a[0].pTab       Pointer to the Table object
    **    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if there is one
    **
    */
    static Table sqlite3SrcListLookup( Parse pParse, SrcList pSrc )
    {
      SrcList_item pItem = pSrc.a[0];
      Table pTab;
      Debug.Assert( pItem != null && pSrc.nSrc == 1 );
      pTab = sqlite3LocateTable( pParse, 0, pItem.zName, pItem.zDatabase );
      sqlite3DeleteTable( pParse.db, ref pItem.pTab );
      pItem.pTab = pTab;
      if ( pTab != null )
      {
        pTab.nRef++;
      }
      if ( sqlite3IndexedByLookup( pParse, pItem ) != 0 )
      {
        pTab = null;
      }
      return pTab;
    }

    /*
    ** Check to make sure the given table is writable.  If it is not
    ** writable, generate an error message and return 1.  If it is
    ** writable return 0;
    */
    static bool sqlite3IsReadOnly( Parse pParse, Table pTab, int viewOk )
    {
      /* A table is not writable under the following circumstances:
      **
      **   1) It is a virtual table and no implementation of the xUpdate method
      **      has been provided, or
      **   2) It is a system table (i.e. sqlite_master), this call is not
      **      part of a nested parse and writable_schema pragma has not
      **      been specified.
      **
      ** In either case leave an error message in pParse and return non-zero.
      */
      if (
         ( IsVirtual( pTab )
          && sqlite3GetVTable( pParse.db, pTab ).pMod.pModule.xUpdate == null )
        || ( ( pTab.tabFlags & TF_Readonly ) != 0
      && ( pParse.db.flags & SQLITE_WriteSchema ) == 0
      && pParse.nested == 0 )
      )
      {
        sqlite3ErrorMsg( pParse, "table %s may not be modified", pTab.zName );
        return true;
      }

#if !SQLITE_OMIT_VIEW
      if ( viewOk == 0 && pTab.pSelect != null )
      {
        sqlite3ErrorMsg( pParse, "cannot modify %s because it is a view", pTab.zName );
        return true;
      }
#endif
      return false;
    }


#if !SQLITE_OMIT_VIEW && !SQLITE_OMIT_TRIGGER
    /*
** Evaluate a view and store its result in an ephemeral table.  The
** pWhere argument is an optional WHERE clause that restricts the
** set of rows in the view that are to be added to the ephemeral table.
*/
    static void sqlite3MaterializeView(
    Parse pParse,      /* Parsing context */
    Table pView,       /* View definition */
    Expr pWhere,       /* Optional WHERE clause to be added */
    int iCur           /* VdbeCursor number for ephemerial table */
    )
    {
      SelectDest dest = new SelectDest();
      Select pDup;
      sqlite3 db = pParse.db;

      pDup = sqlite3SelectDup( db, pView.pSelect, 0 );
      if ( pWhere != null )
      {
        SrcList pFrom;

        pWhere = sqlite3ExprDup( db, pWhere, 0 );
        pFrom = sqlite3SrcListAppend( db, null, null, null );
        //if ( pFrom != null )
        //{
        Debug.Assert( pFrom.nSrc == 1 );
        pFrom.a[0].zAlias = pView.zName;// sqlite3DbStrDup( db, pView.zName );
        pFrom.a[0].pSelect = pDup;
        Debug.Assert( pFrom.a[0].pOn == null );
        Debug.Assert( pFrom.a[0].pUsing == null );
        //}
        //else
        //{
        //  sqlite3SelectDelete( db, ref pDup );
        //}
        pDup = sqlite3SelectNew( pParse, null, pFrom, pWhere, null, null, null, 0, null, null );
      }
      sqlite3SelectDestInit( dest, SRT_EphemTab, iCur );
      sqlite3Select( pParse, pDup, ref dest );
      sqlite3SelectDelete( db, ref pDup );
    }
#endif //* !SQLITE_OMIT_VIEW) && !SQLITE_OMIT_TRIGGER) */

#if (SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !(SQLITE_OMIT_SUBQUERY)
/*
** Generate an expression tree to implement the WHERE, ORDER BY,
** and LIMIT/OFFSET portion of DELETE and UPDATE statements.
**
**     DELETE FROM table_wxyz WHERE a<5 ORDER BY a LIMIT 1;
**                            \__________________________/
**                               pLimitWhere (pInClause)
*/
Expr sqlite3LimitWhere(
Parse pParse,               /* The parser context */
SrcList pSrc,               /* the FROM clause -- which tables to scan */
Expr pWhere,                /* The WHERE clause.  May be null */
ExprList pOrderBy,          /* The ORDER BY clause.  May be null */
Expr pLimit,                /* The LIMIT clause.  May be null */
Expr pOffset,               /* The OFFSET clause.  May be null */
char zStmtType              /* Either DELETE or UPDATE.  For error messages. */
){
Expr pWhereRowid = null;    /* WHERE rowid .. */
Expr pInClause = null;      /* WHERE rowid IN ( select ) */
Expr pSelectRowid = null;   /* SELECT rowid ... */
ExprList pEList = null;     /* Expression list contaning only pSelectRowid */
SrcList pSelectSrc = null;  /* SELECT rowid FROM x ... (dup of pSrc) */
Select pSelect = null;      /* Complete SELECT tree */

/* Check that there isn't an ORDER BY without a LIMIT clause.
*/
if( pOrderBy!=null && (pLimit == null) ) {
sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s", zStmtType);
pParse.parseError = 1;
goto limit_where_cleanup_2;
}

/* We only need to generate a select expression if there
** is a limit/offset term to enforce.
*/
if ( pLimit == null )
{
/* if pLimit is null, pOffset will always be null as well. */
Debug.Assert( pOffset == null );
return pWhere;
}

/* Generate a select expression tree to enforce the limit/offset
** term for the DELETE or UPDATE statement.  For example:
**   DELETE FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
** becomes:
**   DELETE FROM table_a WHERE rowid IN (
**     SELECT rowid FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
**   );
*/

pSelectRowid = sqlite3PExpr( pParse, TK_ROW, null, null, null );
if( pSelectRowid == null ) goto limit_where_cleanup_2;
pEList = sqlite3ExprListAppend( pParse, null, pSelectRowid);
if( pEList == null ) goto limit_where_cleanup_2;

/* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree
** and the SELECT subtree. */
pSelectSrc = sqlite3SrcListDup(pParse.db, pSrc,0);
if( pSelectSrc == null ) {
sqlite3ExprListDelete(pParse.db, pEList);
goto limit_where_cleanup_2;
}

/* generate the SELECT expression tree. */
pSelect = sqlite3SelectNew( pParse, pEList, pSelectSrc, pWhere, null, null,
pOrderBy, 0, pLimit, pOffset );
if( pSelect == null ) return null;

/* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */
pWhereRowid = sqlite3PExpr( pParse, TK_ROW, null, null, null );
if( pWhereRowid == null ) goto limit_where_cleanup_1;
pInClause = sqlite3PExpr( pParse, TK_IN, pWhereRowid, null, null );
if( pInClause == null ) goto limit_where_cleanup_1;

pInClause->x.pSelect = pSelect;
pInClause->flags |= EP_xIsSelect;
sqlite3ExprSetHeight(pParse, pInClause);
return pInClause;

/* something went wrong. clean up anything allocated. */
limit_where_cleanup_1:
sqlite3SelectDelete(pParse.db, pSelect);
return null;

limit_where_cleanup_2:
sqlite3ExprDelete(pParse.db, ref pWhere);
sqlite3ExprListDelete(pParse.db, pOrderBy);
sqlite3ExprDelete(pParse.db, ref pLimit);
sqlite3ExprDelete(pParse.db, ref pOffset);
return null;
}
#endif //* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) */

    /*
** Generate code for a DELETE FROM statement.
**
**     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
**                 \________/       \________________/
**                  pTabList              pWhere
*/
    static void sqlite3DeleteFrom(
    Parse pParse,          /* The parser context */
    SrcList pTabList,      /* The table from which we should delete things */
    Expr pWhere            /* The WHERE clause.  May be null */
    )
    {
      Vdbe v;                /* The virtual database engine */
      Table pTab;            /* The table from which records will be deleted */
      string zDb;            /* Name of database holding pTab */
      int end, addr = 0;     /* A couple addresses of generated code */
      int i;                 /* Loop counter */
      WhereInfo pWInfo;      /* Information about the WHERE clause */
      Index pIdx;            /* For looping over indices of the table */
      int iCur;              /* VDBE VdbeCursor number for pTab */
      sqlite3 db;            /* Main database structure */
      AuthContext sContext;  /* Authorization context */
      NameContext sNC;       /* Name context to resolve expressions in */
      int iDb;               /* Database number */
      int memCnt = -1;        /* Memory cell used for change counting */
      int rcauth;            /* Value returned by authorization callback */

#if !SQLITE_OMIT_TRIGGER
      bool isView;                 /* True if attempting to delete from a view */
      Trigger pTrigger;            /* List of table triggers, if required */
#endif
      sContext = new AuthContext();//memset(&sContext, 0, sizeof(sContext));

      db = pParse.db;
      if ( pParse.nErr != 0 /*|| db.mallocFailed != 0 */ )
      {
        goto delete_from_cleanup;
      }
      Debug.Assert( pTabList.nSrc == 1 );

      /* Locate the table which we want to delete.  This table has to be
      ** put in an SrcList structure because some of the subroutines we
      ** will be calling are designed to work with multiple tables and expect
      ** an SrcList* parameter instead of just a Table* parameter.
      */
      pTab = sqlite3SrcListLookup( pParse, pTabList );
      if ( pTab == null )
        goto delete_from_cleanup;

      /* Figure out if we have any triggers and if the table being
      ** deleted from is a view
      */
#if !SQLITE_OMIT_TRIGGER
      int iDummy;
      pTrigger = sqlite3TriggersExist( pParse, pTab, TK_DELETE, null, out iDummy );
      isView = pTab.pSelect != null;
#else
      const Trigger pTrigger = null;
      bool isView = false;
#endif
#if SQLITE_OMIT_VIEW
//# undef isView
isView = false;
#endif

      /* If pTab is really a view, make sure it has been initialized.
*/
      if ( sqlite3ViewGetColumnNames( pParse, pTab ) != 0 )
      {
        goto delete_from_cleanup;
      }

      if ( sqlite3IsReadOnly( pParse, pTab, ( pTrigger != null ? 1 : 0 ) ) )
      {
        goto delete_from_cleanup;
      }
      iDb = sqlite3SchemaToIndex( db, pTab.pSchema );
      Debug.Assert( iDb < db.nDb );
      zDb = db.aDb[iDb].zName;
#if !SQLITE_OMIT_AUTHORIZATION
rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb);
#else
      rcauth = SQLITE_OK;
#endif
      Debug.Assert( rcauth == SQLITE_OK || rcauth == SQLITE_DENY || rcauth == SQLITE_IGNORE );
      if ( rcauth == SQLITE_DENY )
      {
        goto delete_from_cleanup;
      }
      Debug.Assert( !isView || pTrigger != null );

      /* Assign  cursor number to the table and all its indices.
      */
      Debug.Assert( pTabList.nSrc == 1 );
      iCur = pTabList.a[0].iCursor = pParse.nTab++;
      for ( pIdx = pTab.pIndex; pIdx != null; pIdx = pIdx.pNext )
      {
        pParse.nTab++;
      }

#if !SQLITE_OMIT_AUTHORIZATION
/* Start the view context
*/
if( isView ){
sqlite3AuthContextPush(pParse, sContext, pTab.zName);
}
#endif
      /* Begin generating code.
*/
      v = sqlite3GetVdbe( pParse );
      if ( v == null )
      {
        goto delete_from_cleanup;
      }
      if ( pParse.nested == 0 )
        sqlite3VdbeCountChanges( v );
      sqlite3BeginWriteOperation( pParse, 1, iDb );

      /* If we are trying to delete from a view, realize that view into
      ** a ephemeral table.
      */
#if !(SQLITE_OMIT_VIEW) && !(SQLITE_OMIT_TRIGGER)
      if ( isView )
      {
        sqlite3MaterializeView( pParse, pTab, pWhere, iCur );
      }
#endif
      /* Resolve the column names in the WHERE clause.
      */
      sNC = new NameContext();// memset( &sNC, 0, sizeof( sNC ) );
      sNC.pParse = pParse;
      sNC.pSrcList = pTabList;
      if ( sqlite3ResolveExprNames( sNC, ref pWhere ) != 0 )
      {
        goto delete_from_cleanup;
      }


      /* Initialize the counter of the number of rows deleted, if
** we are counting rows.
*/
      if ( ( db.flags & SQLITE_CountRows ) != 0 )
      {
        memCnt = ++pParse.nMem;
        sqlite3VdbeAddOp2( v, OP_Integer, 0, memCnt );
      }

#if !SQLITE_OMIT_TRUNCATE_OPTIMIZATION
      /* Special case: A DELETE without a WHERE clause deletes everything.
  ** It is easier just to erase the whole table. Prior to version 3.6.5,
  ** this optimization caused the row change count (the value returned by 
  ** API function sqlite3_count_changes) to be set incorrectly.  */
      if ( rcauth == SQLITE_OK && pWhere == null && null == pTrigger && !IsVirtual( pTab )
        && 0 == sqlite3FkRequired( pParse, pTab, null, 0 )
        )
      {
        Debug.Assert( !isView );
        sqlite3VdbeAddOp4( v, OP_Clear, pTab.tnum, iDb, memCnt,
              pTab.zName, P4_STATIC );
        for ( pIdx = pTab.pIndex; pIdx != null; pIdx = pIdx.pNext )
        {
          Debug.Assert( pIdx.pSchema == pTab.pSchema );
          sqlite3VdbeAddOp2( v, OP_Clear, pIdx.tnum, iDb );
        }
      }
      else
#endif //* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
      /* The usual case: There is a WHERE clause so we have to scan through
** the table and pick which records to delete.
*/
      {
        int iRowSet = ++pParse.nMem;    /* Register for rowset of rows to delete */
        int iRowid = ++pParse.nMem;     /* Used for storing rowid values. */
        int regRowid;                   /* Actual register containing rowids */

        /* Collect rowids of every row to be deleted.
        */
        sqlite3VdbeAddOp2( v, OP_Null, 0, iRowSet );
        ExprList elDummy = null;
        pWInfo = sqlite3WhereBegin( pParse, pTabList, pWhere, ref elDummy, WHERE_DUPLICATES_OK );
        if ( pWInfo == null )
          goto delete_from_cleanup;
        regRowid = sqlite3ExprCodeGetColumn( pParse, pTab, -1, iCur, iRowid );
        sqlite3VdbeAddOp2( v, OP_RowSetAdd, iRowSet, regRowid );
        if ( ( db.flags & SQLITE_CountRows ) != 0 )
        {
          sqlite3VdbeAddOp2( v, OP_AddImm, memCnt, 1 );
        }

        sqlite3WhereEnd( pWInfo );

        /* Delete every item whose key was written to the list during the
        ** database scan.  We have to delete items after the scan is complete
        ** because deleting an item can change the scan order. */
        end = sqlite3VdbeMakeLabel( v );

        /* Unless this is a view, open cursors for the table we are 
        ** deleting from and all its indices. If this is a view, then the
        ** only effect this statement has is to fire the INSTEAD OF 
        ** triggers.  */
        if ( !isView )
        {
          sqlite3OpenTableAndIndices( pParse, pTab, iCur, OP_OpenWrite );
        }

        addr = sqlite3VdbeAddOp3( v, OP_RowSetRead, iRowSet, end, iRowid );

        /* Delete the row */
#if !SQLITE_OMIT_VIRTUALTABLE
        if ( IsVirtual( pTab ) )
        {
          VTable pVTab = sqlite3GetVTable( db, pTab );
          sqlite3VtabMakeWritable( pParse, pTab );
          sqlite3VdbeAddOp4( v, OP_VUpdate, 0, 1, iRowid, pVTab, P4_VTAB );
          sqlite3VdbeChangeP5( v, OE_Abort );
          sqlite3MayAbort( pParse );
        }
        else
#endif
        {
          int count = ( pParse.nested == 0 ) ? 1 : 0;    /* True to count changes */
          sqlite3GenerateRowDelete( pParse, pTab, iCur, iRowid, count, pTrigger, OE_Default );
        }

        /* End of the delete loop */
        sqlite3VdbeAddOp2( v, OP_Goto, 0, addr );
        sqlite3VdbeResolveLabel( v, end );

        /* Close the cursors open on the table and its indexes. */
        if ( !isView && !IsVirtual( pTab ) )
        {
          for ( i = 1, pIdx = pTab.pIndex; pIdx != null; i++, pIdx = pIdx.pNext )
          {
            sqlite3VdbeAddOp2( v, OP_Close, iCur + i, pIdx.tnum );
          }
          sqlite3VdbeAddOp1( v, OP_Close, iCur );
        }
      }

      /* Update the sqlite_sequence table by storing the content of the
      ** maximum rowid counter values recorded while inserting into
      ** autoincrement tables.
      */
      if ( pParse.nested == 0 && pParse.pTriggerTab == null )
      {
        sqlite3AutoincrementEnd( pParse );
      }

      /* Return the number of rows that were deleted. If this routine is 
      ** generating code because of a call to sqlite3NestedParse(), do not
      ** invoke the callback function.
      */

      if ( ( db.flags & SQLITE_CountRows ) != 0 && 0 == pParse.nested && null == pParse.pTriggerTab )
      {
        sqlite3VdbeAddOp2( v, OP_ResultRow, memCnt, 1 );
        sqlite3VdbeSetNumCols( v, 1 );
        sqlite3VdbeSetColName( v, 0, COLNAME_NAME, "rows deleted", SQLITE_STATIC );
      }

delete_from_cleanup:
#if !SQLITE_OMIT_AUTHORIZATION
sqlite3AuthContextPop(sContext);
#endif
      sqlite3SrcListDelete( db, ref pTabList );
      sqlite3ExprDelete( db, ref pWhere );
      return;
    }

    /* Make sure "isView" and other macros defined above are undefined. Otherwise
    ** thely may interfere with compilation of other functions in this file
    ** (or in another file, if this file becomes part of the amalgamation).  */
    //#if isView
    // #undef isView
    //#endif
    //#if pTrigger
    // #undef pTrigger
    //#endif

    /*
    ** This routine generates VDBE code that causes a single row of a
    ** single table to be deleted.
    **
    ** The VDBE must be in a particular state when this routine is called.
    ** These are the requirements:
    **
    **   1.  A read/write cursor pointing to pTab, the table containing the row
    **       to be deleted, must be opened as cursor number $iCur.
    **
    **   2.  Read/write cursors for all indices of pTab must be open as
    **       cursor number base+i for the i-th index.
    **
    **   3.  The record number of the row to be deleted must be stored in
    **       memory cell iRowid.
    **
    ** This routine generates code to remove both the table record and all 
    ** index entries that point to that record.
    */
    static void sqlite3GenerateRowDelete(
    Parse pParse,      /* Parsing context */
    Table pTab,        /* Table containing the row to be deleted */
    int iCur,          /* VdbeCursor number for the table */
    int iRowid,        /* Memory cell that contains the rowid to delete */
    int count,         /* If non-zero, increment the row change counter */
    Trigger pTrigger,  /* List of triggers to (potentially) fire */
    int onconf         /* Default ON CONFLICT policy for triggers */
)
    {
      Vdbe v = pParse.pVdbe;          /* Vdbe */
      int iOld = 0;                   /* First register in OLD.* array */
      int iLabel;                     /* Label resolved to end of generated code */

      /* Vdbe is guaranteed to have been allocated by this stage. */
      Debug.Assert( v != null );

      /* Seek cursor iCur to the row to delete. If this row no longer exists 
      ** (this can happen if a trigger program has already deleted it), do
      ** not attempt to delete it or fire any DELETE triggers.  */
      iLabel = sqlite3VdbeMakeLabel( v );
      sqlite3VdbeAddOp3( v, OP_NotExists, iCur, iLabel, iRowid );

      /* If there are any triggers to fire, allocate a range of registers to
      ** use for the old.* references in the triggers.  */
      if ( sqlite3FkRequired( pParse, pTab, null, 0 ) != 0 || pTrigger != null )
      {
        u32 mask;                     /* Mask of OLD.* columns in use */
        int iCol;                     /* Iterator used while populating OLD.* */

        /* TODO: Could use temporary registers here. Also could attempt to
        ** avoid copying the contents of the rowid register.  */
        mask = sqlite3TriggerColmask(
            pParse, pTrigger, null, 0, TRIGGER_BEFORE | TRIGGER_AFTER, pTab, onconf
        );
        mask |= sqlite3FkOldmask( pParse, pTab );
        iOld = pParse.nMem + 1;
        pParse.nMem += ( 1 + pTab.nCol );

        /* Populate the OLD.* pseudo-table register array. These values will be 
        ** used by any BEFORE and AFTER triggers that exist.  */
        sqlite3VdbeAddOp2( v, OP_Copy, iRowid, iOld );
        for ( iCol = 0; iCol < pTab.nCol; iCol++ )
        {
          if ( mask == 0xffffffff || ( mask & ( 1 << iCol ) ) != 0 )
          {
            sqlite3ExprCodeGetColumnOfTable( v, pTab, iCur, iCol, iOld + iCol + 1 );
          }
        }

        /* Invoke BEFORE DELETE trigger programs. */
        sqlite3CodeRowTrigger( pParse, pTrigger,
            TK_DELETE, null, TRIGGER_BEFORE, pTab, iOld, onconf, iLabel
        );

        /* Seek the cursor to the row to be deleted again. It may be that
        ** the BEFORE triggers coded above have already removed the row
        ** being deleted. Do not attempt to delete the row a second time, and 
        ** do not fire AFTER triggers.  */
        sqlite3VdbeAddOp3( v, OP_NotExists, iCur, iLabel, iRowid );

        /* Do FK processing. This call checks that any FK constraints that
        ** refer to this table (i.e. constraints attached to other tables) 
        ** are not violated by deleting this row.  */
        sqlite3FkCheck( pParse, pTab, iOld, 0 );
      }

      /* Delete the index and table entries. Skip this step if pTab is really
      ** a view (in which case the only effect of the DELETE statement is to
      ** fire the INSTEAD OF triggers).  */
      if ( pTab.pSelect == null )
      {
        sqlite3GenerateRowIndexDelete( pParse, pTab, iCur, 0 );
        sqlite3VdbeAddOp2( v, OP_Delete, iCur, ( count != 0 ? (int)OPFLAG_NCHANGE : 0 ) );
        if ( count != 0 )
        {
          sqlite3VdbeChangeP4( v, -1, pTab.zName, P4_TRANSIENT );
        }
      }

      /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
      ** handle rows (possibly in other tables) that refer via a foreign key
      ** to the row just deleted. */
      sqlite3FkActions( pParse, pTab, null, iOld );

      /* Invoke AFTER DELETE trigger programs. */
      sqlite3CodeRowTrigger( pParse, pTrigger,
          TK_DELETE, null, TRIGGER_AFTER, pTab, iOld, onconf, iLabel
      );

      /* Jump here if the row had already been deleted before any BEFORE
      ** trigger programs were invoked. Or if a trigger program throws a 
      ** RAISE(IGNORE) exception.  */
      sqlite3VdbeResolveLabel( v, iLabel );
    }


    /*
    ** This routine generates VDBE code that causes the deletion of all
    ** index entries associated with a single row of a single table.
    **
    ** The VDBE must be in a particular state when this routine is called.
    ** These are the requirements:
    **
    **   1.  A read/write cursor pointing to pTab, the table containing the row
    **       to be deleted, must be opened as cursor number "iCur".
    **
    **   2.  Read/write cursors for all indices of pTab must be open as
    **       cursor number iCur+i for the i-th index.
    **
    **   3.  The "iCur" cursor must be pointing to the row that is to be
    **       deleted.
    */
    static void sqlite3GenerateRowIndexDelete(
    Parse pParse,     /* Parsing and code generating context */
    Table pTab,       /* Table containing the row to be deleted */
    int iCur,         /* VdbeCursor number for the table */
    int nothing       /* Only delete if aRegIdx!=0 && aRegIdx[i]>0 */
    )
    {
      int[] aRegIdx = null;
      sqlite3GenerateRowIndexDelete( pParse, pTab, iCur, aRegIdx );
    }
    static void sqlite3GenerateRowIndexDelete(
    Parse pParse,     /* Parsing and code generating context */
    Table pTab,       /* Table containing the row to be deleted */
    int iCur,          /* VdbeCursor number for the table */
    int[] aRegIdx       /* Only delete if aRegIdx!=0 && aRegIdx[i]>0 */
    )
    {
      int i;
      Index pIdx;
      int r1;

      for ( i = 1, pIdx = pTab.pIndex; pIdx != null; i++, pIdx = pIdx.pNext )
      {
        if ( aRegIdx != null && aRegIdx[i - 1] == 0 )
          continue;
        r1 = sqlite3GenerateIndexKey( pParse, pIdx, iCur, 0, false );
        sqlite3VdbeAddOp3( pParse.pVdbe, OP_IdxDelete, iCur + i, r1, pIdx.nColumn + 1 );
      }
    }

    /*
    ** Generate code that will assemble an index key and put it in register
    ** regOut.  The key with be for index pIdx which is an index on pTab.
    ** iCur is the index of a cursor open on the pTab table and pointing to
    ** the entry that needs indexing.
    **
    ** Return a register number which is the first in a block of
    ** registers that holds the elements of the index key.  The
    ** block of registers has already been deallocated by the time
    ** this routine returns.
    */
    static int sqlite3GenerateIndexKey(
    Parse pParse,     /* Parsing context */
    Index pIdx,       /* The index for which to generate a key */
    int iCur,         /* VdbeCursor number for the pIdx.pTable table */
    int regOut,       /* Write the new index key to this register */
    bool doMakeRec    /* Run the OP_MakeRecord instruction if true */
    )
    {
      Vdbe v = pParse.pVdbe;
      int j;
      Table pTab = pIdx.pTable;
      int regBase;
      int nCol;

      nCol = pIdx.nColumn;
      regBase = sqlite3GetTempRange( pParse, nCol + 1 );
      sqlite3VdbeAddOp2( v, OP_Rowid, iCur, regBase + nCol );
      for ( j = 0; j < nCol; j++ )
      {
        int idx = pIdx.aiColumn[j];
        if ( idx == pTab.iPKey )
        {
          sqlite3VdbeAddOp2( v, OP_SCopy, regBase + nCol, regBase + j );
        }
        else
        {
          sqlite3VdbeAddOp3( v, OP_Column, iCur, idx, regBase + j );
          sqlite3ColumnDefault( v, pTab, idx, -1 );
        }
      }
      if ( doMakeRec )
      {
        string zAff;
        if ( pTab.pSelect != null|| ( pParse.db.flags & SQLITE_IdxRealAsInt ) != 0 )
        {
          zAff = "";
        }
        else
        {
          zAff = sqlite3IndexAffinityStr( v, pIdx );
        }
        sqlite3VdbeAddOp3( v, OP_MakeRecord, regBase, nCol + 1, regOut );
        sqlite3VdbeChangeP4( v, -1, zAff, P4_TRANSIENT );
      }
      sqlite3ReleaseTempRange( pParse, regBase, nCol + 1 );
      return regBase;
    }
  }
}
